package com.wbs.domain.repository.base;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.PersistenceProvider;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import com.wbs.domain.base.BaseEntity;

@NoRepositoryBean
public class BaseRepositoryImpl<T, ID extends Serializable> extends
        SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

    @SuppressWarnings("unused")
    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityManager em;
    @SuppressWarnings("unused")
    private final PersistenceProvider provider;

    private Class<?> springDataRepositoryInterface;

    public Class<?> getSpringDataRepositoryInterface() {
        return springDataRepositoryInterface;
    }

    public void setSpringDataRepositoryInterface(
            Class<?> springDataRepositoryInterface) {
        this.springDataRepositoryInterface = springDataRepositoryInterface;
    }

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getMetadata(domainClass, em), em, null);
    }

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager, Class<?> springDataRepositoryInterface) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.em = entityManager;
        this.provider = PersistenceProvider.fromEntityManager(entityManager);
        this.springDataRepositoryInterface = springDataRepositoryInterface;
    }

    public T findSingleObject(String queryString, Object... params) {
        try {
            List<T> list = executeQuery(queryString, null, null, params);
            if (list.size() != 1 || list.get(0) == null) {
                throw new Exception("Multiply or no entries.");
            }
            return list.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> executeNativeQuery(String queryString,
            Integer startPosition, Integer maxResult, String[] resultFields,
            Object... params) {
        Query query = this.em.createNativeQuery(queryString);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]);
            }
        }
        if (startPosition != null) {
            query.setFirstResult(startPosition);
        }
        if (maxResult != null) {
            query.setMaxResults(maxResult);
        }
        try {
            List<Object[]> list = query.getResultList();
            List<Map<String, Object>> returnList = new ArrayList<Map<String, Object>>();
            for (Object[] obj : list) {
                Map<String, Object> map = new HashMap<String, Object>();
                int i = 0;
                for (String field : resultFields) {
                    map.put(field, obj[i++]);
                }
                returnList.add(map);
            }
            return returnList;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public int executeNativeQuery(String queryString, Object... params) {
        Query query = this.em.createNativeQuery(queryString);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]);
            }
        }
        try {
            return query.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }

    @SuppressWarnings("unchecked")
    public List<T> executeQuery(String queryString, Integer startPosition,
            Integer maxResult, Object... params) {
        Query query = this.em.createQuery(queryString);
        query.setHint("org.hibernate.cacheable", true);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]);
            }
        }
        if (startPosition != null) {
            query.setFirstResult(startPosition);
        }
        if (maxResult != null) {
            query.setMaxResults(maxResult);
        }
        try {
            return ((List<T>) query.getResultList());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public List<T> executeListQuery(String queryString, Integer startPosition,
            Integer maxResult, Map<String,Object> params) {
        Query query = this.em.createQuery(queryString);
        query.setHint("org.hibernate.cacheable", true);
        if (params != null) {
            Set<String> keyParams = params.keySet();
            for (String key : keyParams) {
                query.setParameter(key, params.get(key));
            }
        }
        if (startPosition != null) {
            query.setFirstResult(startPosition);
        }
        if (maxResult != null) {
            query.setMaxResults(maxResult);
        }
        try {
            return ((List<T>) query.getResultList());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Long executeCountQuery(String queryString, Integer startPosition,
            Integer maxResult, Map<String,Object> params) {
        Query query = this.em.createQuery(queryString);
        query.setHint("org.hibernate.cacheable", true);
        if (params != null) {
            Set<String> keyParams = params.keySet();
            for (String key : keyParams) {
                query.setParameter(key, params.get(key));
            }
        }
        if (startPosition != null) {
            query.setFirstResult(startPosition);
        }
        if (maxResult != null) {
            query.setMaxResults(maxResult);
        }
        try {
            return  (Long)query.getSingleResult();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Map<String, Object> createQueryString(String entityName,
            String propertyName, Map<String, Object> attr) {
        String queryString = "SELECT x"
                + ((propertyName != null && !"".equals(propertyName)) ? "."
                        + propertyName : "") + " FROM " + entityName + " x";
        String whereString = "";
        Object[] params = null;
        if (attr != null && !attr.isEmpty()) {
            List<Object> paramsList = new ArrayList<Object>();
            Set<String> keys = attr.keySet();
            for (String key : keys) {
                if (!"".equals(whereString)) {
                    whereString = whereString + " AND";
                }
                whereString = whereString + " x." + key + " = ?";
                paramsList.add(attr.get(key));
            }
            params = new Object[paramsList.size()];
            params = paramsList.toArray(params);
        }
        if (!"".equals(whereString)) {
            queryString = queryString + " WHERE" + whereString;
        }

        Map<String, Object> result = new HashMap<String, Object>();
        result.put("queryString", queryString);
        result.put("params", params);
        return result;
    }

    @SuppressWarnings("unchecked")
    public Map<String, Object> createParameterizedQueryString(
            String entityName, String propertyName, Map<String, Object> attr) {
        String queryString = "SELECT x"
                + ((propertyName != null && !"".equals(propertyName)) ? "."
                        + propertyName : "") + " FROM " + entityName + " x";
        String whereString = "";
        Map<String, Object> params = null;
        if (attr != null && !attr.isEmpty()) {
            params = new HashMap<String, Object>();
            for (String key : attr.keySet()) {
                if (!"".equals(whereString)) {
                    whereString = whereString + " AND";
                }

                if (attr.get(key) instanceof Map) {
                    Map<String, Object> range = (Map<String, Object>) attr
                            .get(key);
                    Object[] keys = range.keySet().toArray();
                    if (keys.length != 2) {
                        continue;
                    }

                    String fromKey = (String) keys[0];
                    String thruKey = (String) keys[1];
                    if (!fromKey.startsWith("from")
                            || !thruKey.startsWith("thru")) {
                        fromKey = (String) keys[1];
                        thruKey = (String) keys[0];
                    }

                    if (range.get(thruKey) == null) {
                        whereString = whereString + " x." + key + " >= :"
                                + fromKey;
                        params.put(fromKey, range.get(fromKey));
                    } else if (range.get(fromKey) == null) {
                        whereString = whereString + " x." + key + " <= :"
                                + thruKey;
                        params.put(thruKey, range.get(thruKey));
                    } else {
                        whereString = whereString + " x." + key + " BETWEEN :"
                                + fromKey + " AND :" + thruKey;
                        params.put(fromKey, range.get(fromKey));
                        params.put(thruKey, range.get(thruKey));
                    }

                } else {
                    whereString = whereString + " x." + key + " = :" + key;
                    params.put(key, attr.get(key));
                }
            }
        }
        if (!"".equals(whereString)) {
            queryString = queryString + " WHERE" + whereString;
        }

        Map<String, Object> result = new HashMap<String, Object>();
        result.put("queryString", queryString);
        result.put("params", params);
        return result;
    }

    public <E extends BaseEntity> E saveEntity(E entity){
        if(entity.getCodigo()==null){
            em.persist(entity);
        }else{
            entity=em.merge(entity);
        }
        return entity;
    }

    public <E extends BaseEntity> List<E> saveEntities(Iterable<E> entities){
        List<E> savedEntities=new ArrayList<E>();
        for(E entity:entities){
            savedEntities.add(saveEntity(entity));
        }
        return savedEntities;
    }
    
    public <E extends BaseEntity> E find(Class<E> classType,Serializable id){
        E object=em.find(classType, id);
        return object;
    }

    public <E extends BaseEntity> List<E> findAll(Class<E> classType) {
        CriteriaBuilder builder=em.getEntityManagerFactory().getCriteriaBuilder();
        CriteriaQuery<E> cq=builder.createQuery(classType);
        List<E> loadedEntities=em.createQuery(cq.select(cq.from(classType))).getResultList();
        return loadedEntities;
    }
}